
package PolacaInversa;

import Mensajes.Mensaje;
import TablaDeSimbolos.TSimbolo;
import TokensDelLenguaje.Token;
import TokensDelLenguaje.Token_Cadena;
import TokensDelLenguaje.Token_Cmp;
import TokensDelLenguaje.Token_Operador;
import TokensDelLenguaje.Token_OperadorSuma;
import TokensDelLenguaje.Token_Pos;
import TokensDelLenguaje.Token_Print;
import TokensDelLenguaje.Token_Registro;
import TokensDelLenguaje.Token_Struct;
import TokensDelLenguaje.Token_Variable;
import TokensDelLenguaje.Tokens;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.Observable;

public class Polaca extends Observable implements Cloneable{
    
    private ArrayList<Token> elementosPolaca;
    private Pila pila;
    private Pila polaca;
    private TSimbolo ts;
    public BancoRegistros banco;
    private int posCorriente; 
    private boolean salto_fin;
    
    
    public Polaca(TSimbolo ts){
        elementosPolaca = new ArrayList<Token>();
        this.ts = ts;
        banco = new BancoRegistros();
        pila = new Pila();
        polaca = new Pila();
        this.posCorriente = -1;
        this.salto_fin = false;
    }
    
    public boolean traducir(){
        this.colocarEncabezado();
        for(int i=0; i<elementosPolaca.size(); i++){
            if(elementosPolaca.get(i).getId() == Tokens.posicionSalto){
                int salto = ((Token_Pos)elementosPolaca.get(i)).getPos();
                if(salto < elementosPolaca.size())
                    elementosPolaca.get(salto).setSalto(true);
                else
                    salto_fin = true;
            }
        }
            for(int i=0; i<elementosPolaca.size(); i++){
            Token t = elementosPolaca.get(i);
            if(elementosPolaca.get(i).getSalto()){
                this.mostrar_mensaje("@Label" + i + ":\n", Mensaje.COD);
            }
            Token o2;
            Token o1;
            Registro r = null;
            
            ArrayList l = new ArrayList(2);
            String s = "";
            int linea;
            switch (t.getId()){
                case const_uinteger:
                case const_ulongint:
                case identificador:
                case registro:
                case posicionSalto:
                case cadena:
                    pila.apilar(t);
                    l.add(true);
                    l.add("");
                break;
                case punto:
                    o1 = (Token) pila.desapilar();
                    o2 = (Token) pila.desapilar();
                    linea = o1.getLinea();
                    if(ts.existeToken(o2.getCadena(), o1.getCadena())){
                        o1 = ts.get(o2.getCadena(), o1.getCadena());
                        o1.setLinea(linea);
                    }
                    else{
                        if(!o1.esRegistro()){
                            l.add(false);
                            l.add("Error en Gen. de Codigo: linea " + linea + " - Variable '" + o1.getCadena() + "' no declarada.");
                            break;
                        }
                    }
                    
                    linea = o2.getLinea();
                    if(ts.existeToken(o2.getCadena())){
                        o2 = ts.get(o2.getCadena());
                        o2.setLinea(linea);
                    }
                    else{
                        if(!o2.esRegistro()){
                            l.add(false);
                            l.add("Error en Gen. de Codigo: linea " + linea + " - Variable '" + o2.getCadena() + "' no declarada.");
                            break;
                        }
                    }
                    
                    o1 = ((Token_Struct)o2).getElementos(o1.getNombre());
                    
                    r = banco.getLibre(t, o1, o2);
                    
                    l = ((Token_Operador)t).traducir_assembler(o1, o2, r);
                    
                    pila.apilar(o1);
                    /*Token_Registro tr = new Token_Registro(r);
                    tr.setLinea(o1.getLinea());
                    pila.apilar(tr);*/
                break;
                case suma:
                case resta:
          
                    o1 = (Token) pila.desapilar();
                    o2 = (Token) pila.desapilar();
                    linea = o1.getLinea();
                    if(ts.existeToken(o1.getCadena())){
                        o1 = ts.get(o1.getCadena());
                        o1.setLinea(linea);
                    }
                    else{
                        if(!o1.esRegistro()){
                            l.add(false);
                            l.add("Error en Gen. de Codigo: linea " + linea + " - Variable '" + o1.getCadena() + "' no declarada.");
                            break;
                        }
                    }
                    
                    linea = o2.getLinea();
                    if(ts.existeToken(o2.getCadena())){
                        o2 = ts.get(o2.getCadena());
                        o2.setLinea(linea);
                    }
                    else{
                        if(!o2.esRegistro()){
                            l.add(false);
                            l.add("Error en Gen. de Codigo: linea " + linea + " - Variable '" + o2.getCadena() + "' no declarada.");
                            break;
                        }
                    }
                    r = banco.getLibre(t, o1, o2);
                    l = ((Token_Operador)t).traducir_assembler(o1,o2,r);
                    Token_Registro tr = new Token_Registro(r);
                    tr.setLinea(o1.getLinea());
                    pila.apilar(tr); 
                break;
                case division:
                    
                    o1 = (Token) pila.desapilar();
                    o2 = (Token) pila.desapilar();
                    linea = o1.getLinea();
                    if(ts.existeToken(o1.getCadena())){
                        o1 = ts.get(o1.getCadena());
                        o1.setLinea(linea);
                    }
                    else{
                        if(!o1.esRegistro()){
                            l.add(false);
                            l.add("Error en Gen. de Codigo: linea " + linea + " - Variable '" + o1.getCadena() + "' no declarada.");
                            break;
                        }
                    }
                   
                    linea = o2.getLinea();
                    if(ts.existeToken(o2.getCadena())){
                        o2 = ts.get(o2.getCadena());
                        o2.setLinea(linea);
                    }
                    else{
                        if(!o2.esRegistro()){
                            l.add(false);
                            l.add("Error en Gen. de Codigo: linea " + linea + " - Variable '" + o2.getCadena() + "' no declarada.");
                            break;
                        }
                    }
                   
                    r = banco.getLibre(t, o1, o2);
                    
                    l = ((Token_Operador)t).traducir_assembler(o1,o2,r);
                    
                    tr = new Token_Registro(r);
                    tr.setLinea(o1.getLinea());
                    pila.apilar(tr); 
                break;
                case asterisco:
                    o1 = (Token) pila.desapilar();
                    o2 = (Token) pila.desapilar();
                    
                    linea = o1.getLinea();
                    if(ts.existeToken(o1.getCadena())){
                        o1 = ts.get(o1.getCadena());
                        o1.setLinea(linea);
                    }
                    else{
                        if(!o1.esRegistro()){
                            l.add(false);
                            l.add("Error en Gen. de Codigo: linea " + linea + " - Variable '" + o1.getCadena() + "' no declarada.");
                            break;
                        }
                    }
                    
                    linea = o2.getLinea();
                    if(ts.existeToken(o2.getCadena())){
                        o2 = ts.get(o2.getCadena());
                        o2.setLinea(linea);
                    }
                    else{
                        if(!o2.esRegistro()){
                            l.add(false);
                            l.add("Error en Gen. de Codigo: linea " + linea + " - Variable '" + o2.getCadena() + "' no declarada.");
                            break;
                        }
                    }
                    
                    r = banco.getLibre(t, new Token(o1.getId()), new Token(o2.getId()));
                    
                    l = ((Token_Operador)t).traducir_assembler(o1,o2,r);
                    if(o2.esRegistro())
                        banco.liberar(((Token_Registro)o2).getRegistro());
                    tr = new Token_Registro(r);
                    tr.setLinea(o1.getLinea());
                    pila.apilar(tr); 
                break;
                case rsv_print:
                    o1 = (Token) pila.desapilar();
                    l = ((Token_Print)t).traducir_assembler(o1);
                break;
                case asignacion:
                    o1 = (Token) pila.desapilar();
                    o2 = (Token) pila.desapilar();
                    
                    linea = o1.getLinea();
                    if(ts.existeToken(o1.getCadena())){
                        o1 = ts.get(o1.getCadena());
                        o1.setLinea(linea);
                    }
                    else{
                        if(!o1.esRegistro()){
                            l.add(false);
                            l.add("Error en Gen. de Codigo: linea " + linea + " - Variable '" + o1.getCadena() + "' no declarada.");
                            break;
                        }
                    }
                    linea = o2.getLinea();
                    if(ts.existeToken(o2.getCadena())){
                        o2 = ts.get(o2.getCadena());
                        o2.setLinea(linea);
                    }
                    else{
                        if(!o2.esRegistro()){
                            l.add(false);
                            l.add("Error en Gen. de Codigo: linea " + linea + " - Variable '" + o2.getCadena() + "' no declarada.");
                            break;
                        }
                    }
                    // Los busco en la TS para saber el tipo de los
                    // identificadores.
                    r = banco.getLibre(t, o1, o2);
                    l = ((Token_Operador)t).traducir_assembler(o2,o1,r);
                    this.banco.liberar(r);
                break;
                case cmp:
                    o1 = (Token) pila.desapilar();
                    o2 = (Token) pila.desapilar();
                    linea = o1.getLinea();
                    if(ts.existeToken(o1.getCadena())){
                        o1 = ts.get(o1.getCadena());
                        o1.setLinea(linea);
                    }
                    else{
                        if(!o1.esRegistro()){
                            l.add(false);
                            l.add("Error en Gen. de Codigo: linea " + linea + " - Variable '" + o1.getCadena() + "' no declarada.");
                            break;
                        }
                    }
                    linea = o2.getLinea();
                    if(ts.existeToken(o2.getCadena())){
                        o2 = ts.get(o2.getCadena());
                        o2.setLinea(linea);
                    }
                    else{
                        if(!o2.esRegistro()){
                            l.add(false);
                            l.add("Error en Gen. de Codigo: linea " + linea + " - Variable '" + o2.getCadena() + "' no declarada.");
                            break;
                        }
                    }
                    r = banco.getLibre(t, o1, o2);
                    l = ((Token_Cmp)t).traducir_assembler(o1, o2, r);
                    banco.liberar(r);
                break;
                case salto_incondicional:
                    s = "";
                    o1 = (Token) pila.desapilar();
                    s += "JMP @Label" + o1.getCadena() + "\n";
                    l.add(true);
                    l.add(s);
                break;
                case mayor:
                    o1 = (Token) pila.desapilar();
                    s = "JG @Label" + o1.getCadena() + "\n"; 
                    l.add(true);
                    l.add(s);
                break;
                case mayor_igual:
                    o1 = (Token) pila.desapilar();
                    s = "JGE @Label" + o1.getCadena() + "\n"; 
                    l.add(true);
                    l.add(s);
                break;
                case menor:
                    o1 = (Token) pila.desapilar();
                    s = "JB @Label" + o1.getCadena() + "\n"; 
                    l.add(true);
                    l.add(s);
                break;
                case menor_igual:
                    o1 = (Token) pila.desapilar();
                    s = "JBE @Label" + o1.getCadena() + "\n"; 
                    l.add(true);
                    l.add(s);
                break;
                case igual:
                    o1 = (Token) pila.desapilar();
                    s = "JE @Label" + o1.getCadena() + "\n"; 
                    l.add(true);
                    l.add(s);
                break;
                case distinto:
                    o1 = (Token) pila.desapilar();
                    s = "JNE @Label" + o1.getCadena() + "\n"; 
                    l.add(true);
                    l.add(s);
                break;                    
                case ilegal:
                    l.add(false);
                    l.add("Error en Gen. de Codigo: Token Ilegal");
            }
            String mensaje = (String)l.get(1);
            
            if(!(Boolean)l.get(0)){
                i = elementosPolaca.size();  
                this.mostrar_mensaje(mensaje, Mensaje.INF);
                return false;
            }
            else
                this.mostrar_mensaje(mensaje, Mensaje.COD);
        }
        if(salto_fin)
            this.mostrar_mensaje("@Label" + elementosPolaca.size() + ":\n", Mensaje.COD);
        this.colocarFinal();
        return true;
    }
    
    private void colocarEncabezado() {
        String encabezado = ".386\n";
        encabezado += ".model flat, stdcall\n";
        encabezado += "option casemap :none\n";
        encabezado += "include \\masm32\\include\\windows.inc\n";
        encabezado += "include \\masm32\\include\\kernel32.inc\n";
        encabezado += "include \\masm32\\include\\user32.inc\n";
        encabezado += "includelib \\masm32\\lib\\kernel32.lib\n";
        encabezado += "includelib \\masm32\\lib\\user32.lib\n";
        encabezado += ".stack 200\n";
        encabezado += ".data\n";
        this.mostrar_mensaje(encabezado, Mensaje.COD);
        this.colocarDatos();
        encabezado = ".code\n";
        encabezado += "start:\n";       
        this.mostrar_mensaje(encabezado, Mensaje.COD);
    }

    private void colocarDatos(){
        Collection<Token> c = ts.values();
        String datos = "";
        datos += "__error_resta db \"RESULTADO NEGATIVO EN RESTA\", 0\n";
        datos += "__error_overflow db \"OVERFLOW EN PRODUCTO\", 0\n";
        datos += "__error_division db \"ERROR EN DIVISION\", 0\n";
        datos += "__error db \"ERROR\", 0\n";
        for(Iterator<Token> it = c.iterator(); it.hasNext();){
            Token t = it.next();
            String linea = "";
            switch(t.getId()){
            case cadena:
              linea = ((Token_Cadena)t).getNombre() + " db \"" + t.getCadena() + "\", 0\n"; 
              break;
            case struct:
                linea = ((Token_Cadena)t).getNombre();
                linea += " db " + ((Token_Struct)t).getTamano() + " DUP(?)\n";
              break;            
            case var:
                linea = ((Token_Cadena)t).getNombre();
                if(t.getLong() != null)
                    if (t.getId() == Tokens.const_uinteger)
                        linea += " dw " + t.getLong() + "\n";
                    else
                        linea += " dd " + t.getLong() + "\n";
                else
                    linea += " ds " + ((Token_Variable)t).getTamano() + "\n";
              break;
            case const_ulongint:
                linea = "_" + t.getCadena();
                if(t.getLong() != null)
                    linea += " dd " + t.getLong() + "\n";
                else
                    linea += " dd ?\n";
              break;
            case const_uinteger:
                linea = "_" + t.getCadena();
                if(t.getLong() != null)
                    linea += " dw " + t.getLong() + "\n";
                else
                    linea += " dw ?\n";
              break;
          }
            datos += linea;
        }
        this.mostrar_mensaje(datos, Mensaje.COD);
    }
    
    private void colocarFinal(){
        String fin = "jmp fin\n";

        fin += "overflow:\n";
        fin += "invoke MessageBox, NULL, addr __error_overflow, addr __error, MB_OK\n";
        fin += "invoke ExitProcess, 0\n";
        //fin += "jmp fin\n";

        fin += "res_negativo:\n";
        fin += "invoke MessageBox, NULL, addr __error_resta, addr __error, MB_OK\n";
        fin += "invoke ExitProcess, 0\n";
        //fin += "int 21h\n";
        
        fin += "division:\n";
        fin += "invoke MessageBox, NULL, addr __error_division, addr __error, MB_OK\n";
        fin += "invoke ExitProcess, 0\n";
        
        fin += "fin:\n";
        //fin += "invoke MessageBox, NULL, addr __error_overflow, addr __error, MB_OK\n";
        fin += "invoke ExitProcess, 0\n";
        fin += "end start";
        
        this.mostrar_mensaje(fin, Mensaje.COD);
    }
    
    
    private void mostrar_mensaje(String mensaje, int tipo) {
        Mensaje m = new Mensaje(mensaje, tipo);
        setChanged();
        notifyObservers(m);
    }
    
    public Object desapilar(){
        return this.polaca.desapilar();
    }
    
    public void apilar(Object pos){
        this.polaca.apilar(pos);
    }

    public void addElemento (Token t){
        this.elementosPolaca.add(t);
        this.posCorriente = this.posCorriente + 1;
    }
    
    public int posActual(){
        return this.posCorriente;
    }
    
    public void escribir(int pos, Token numeroSalto){
        this.elementosPolaca.set(pos, numeroSalto);
    }
    
    public void setPosicionmenos1(){
        this.posCorriente = this.posCorriente - 1;
    }

    public ArrayList<Token> getElementos() {
        return elementosPolaca;
    }

    public void addElementos(ArrayList<Token> lista) {
        this.elementosPolaca.addAll(lista);
        this.posCorriente = this.elementosPolaca.size()-1;
    }
}
